import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:netease_cloud_music_api/utils/crypto.dart';
import 'package:netease_cloud_music_api/utils/player_request.dart';
import 'package:shared_preferences/shared_preferences.dart';

class PlayerRequest {
  static Dio _dio = Dio();
  late SharedPreferences _sharedPreferences;

  static String _chooseUserAgent({UserAgent ua = UserAgent.random}) {
    List userAgentList = [
      // iOS 13.5.1 14.0 beta with safari
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1',
      'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.',
      // iOS with qq micromsg
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML like Gecko) Mobile/14A456 QQ/6.5.7.408 V1_IPH_SQ_6.5.7_1_APP_A Pixel/750 Core/UIWebView NetType/4G Mem/103',
      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.15(0x17000f27) NetType/WIFI Language/zh',
      // Android -> Huawei Xiaomi
      'Mozilla/5.0 (Linux; Android 9; PCT-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.64 HuaweiBrowser/10.0.3.311 Mobile Safari/537.36',
      'Mozilla/5.0 (Linux; U; Android 9; zh-cn; Redmi Note 8 Build/PKQ1.190616.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.141 Mobile Safari/537.36 XiaoMi/MiuiBrowser/12.5.22',
      // Android + qq micromsg
      'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.62 XWEB/2581 MMWEBSDK/200801 Mobile Safari/537.36 MMWEBID/3027 MicroMessenger/7.0.18.1740(0x27001235) Process/toolsmp WeChat/arm64 NetType/WIFI Language/zh_CN ABI/arm64',
      'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; BKK-AL10 Build/HONORBKK-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.6 Mobile Safari/537.36',
      // macOS 10.15.6  Firefox / Chrome / Safari
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15',
      // Windows 10 Firefox / Chrome / Edge
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.30 Safari/537.36',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586',
      // Linux 就算了
    ];
    final random = Random();
    switch (ua) {
      case UserAgent.random:
        return userAgentList[random.nextInt(userAgentList.length)];
      case UserAgent.mobile:
        return userAgentList[random.nextInt(9)];
      case UserAgent.pc:
        return userAgentList[random.nextInt(6) + 8];
    }
    return '';
  }

  PlayerRequest({required String url, Map<String, dynamic>? data, required PlayerRequestOptions options})
  {
    final PlayerRequest that = this;
    data = data ?? {};
    _dio.interceptors.add(InterceptorsWrapper(onRequest: (RequestOptions requestOptions, RequestInterceptorHandler handler) {
      _dio.lock();
      SharedPreferences.getInstance().then((SharedPreferences sharedPreferences) {
        that._sharedPreferences = sharedPreferences;
        RequestOptions _options = requestOptions;
        Map<String, dynamic> headers = {HttpHeaders.userAgentHeader: _chooseUserAgent(ua: options.ua ?? UserAgent.random)};
        if (url.indexOf('music.163.com') > -1) headers[HttpHeaders.refererHeader] = 'https://music.163.com';
        if (options.realIP != null) headers['x-real-ip'] = options.realIP;
        if (options.method == PlayerRequestMethod.post) {
          headers[HttpHeaders.contentTypeHeader] = Headers.formUrlEncodedContentType;
          _options.method = 'POST';
        } else if (options.method == PlayerRequestMethod.get) {
          _options.method = 'GET';
        }
        // Cookie 设置
        if (options.cookie != null) {
          headers[HttpHeaders.cookieHeader] = _getCookie(cookie: options.cookie, sharedPreferences: sharedPreferences);
        } else {
          headers[HttpHeaders.cookieHeader] = _getCookie(sharedPreferences: sharedPreferences);
        }
        // 参数加密
        if (options.crypto == EncryptType.weapi) {
          List? csrfToken;
          if (headers[HttpHeaders.cookieHeader] != null) {
            String? csrfTokenRegExp = RegExp(r'__csrf=([^(;|$)]+)').stringMatch(headers[HttpHeaders.cookieHeader]);
            csrfToken = csrfTokenRegExp?.split('=');
          }
          data!['csrf_token'] = csrfToken != null ? csrfToken[1] : '';
          data = NeteaseCloudMusicApiCrypto.weapi(object: data);
          url = url.replaceFirst(RegExp(r'\w*api'), 'weapi');
        } else if (options.crypto == EncryptType.linuxapi) {
          data = NeteaseCloudMusicApiCrypto.linuxapi(object: {
            'method': options.method == PlayerRequestMethod.post ? 'POST' : 'GET',
            'url': url,
            'params': data,
          });
          headers[HttpHeaders.userAgentHeader] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36';
          url = 'https://music.163.com/api/linux/forward';
        } else if (options.crypto == EncryptType.eapi) {
          Map<String, String>? cookiesMap = {};
          if (headers[HttpHeaders.cookieHeader] != null) {
            cookiesMap = headers[HttpHeaders.cookieHeader].split(';').fold(<String, String>{}, (map, element) {
              List<String> data = element.split('=');
              if (data.length >= 2) map[data[0]] = data[1];
              return map;
            });
          }
          Map<String, dynamic> header = {
            'osver': cookiesMap!['osver'] ?? '',
            'deviceId': cookiesMap['deviceId'],
            'appver': cookiesMap['appver'] ?? '7.3.20',
            'versioncode': cookiesMap['versioncode'] ?? '140',
            'mobilename': cookiesMap['mobilename'],
            'buildver': cookiesMap['buildver'] ?? DateTime.now().millisecondsSinceEpoch ~/ 1000,
            'resolution': cookiesMap['resolution'] ?? '1920x1080',
            '__csrf': cookiesMap['__csrf'] ?? '',
            'os': cookiesMap['os'] ?? 'iphone',
            'channel': cookiesMap['channel'],
            'requestId': '${DateTime.now().millisecondsSinceEpoch}${Random().nextInt(1000).toString().padLeft(4, '0')}',
          };
          if (cookiesMap.containsKey('MUSIC_U')) header['MUSIC_U'] = cookiesMap['MUSIC_U'];
          if (cookiesMap.containsKey('MUSIC_A')) header['MUSIC_A'] = cookiesMap['MUSIC_A'];
          data!['header'] = header;
          data = NeteaseCloudMusicApiCrypto.eapi(url: options.url, object: data);
          url = url.replaceFirst(RegExp(r'\w*api'), 'eapi');
        }
        _options.data = data;
        _options.baseUrl = url;
        _options.headers = headers;
        return handler.next(_options);
      }).whenComplete(() => _dio.unlock());
    }, onResponse: (Response response, ResponseInterceptorHandler handler) {
      handler.next(response);
    }));
  }

  Future<PlayerRequestResponse<dynamic>> _requestResponse(Response response) async {
    PlayerRequestResponse playerRequestResponse = PlayerRequestResponse();
    if (response.headers['set-cookie'] != null) {
      Map? cookie = _setCookie(response.headers['set-cookie']!);
      _sharedPreferences.setString('cookie', json.encode(cookie));
      playerRequestResponse.cookie = cookie;
    } else {
      String? getCookie = _getCookie(sharedPreferences: _sharedPreferences);
      Map<String, dynamic> _cookie = Map<String, dynamic>();
      print('object');
      print(getCookie);
      if (getCookie != null) {
        List _tempCookie = getCookie.split(';');
        _tempCookie.forEach((element) {
          List _cookieValue = element.toString().split('=');
          _cookie[_cookieValue[0]] = _cookieValue[1];
        });
      }
      playerRequestResponse.cookie =  _cookie;
    }
    playerRequestResponse.body = response.data is Map ? jsonEncode(response.data) : response.data;
    if (playerRequestResponse.body is String) {
      playerRequestResponse.status = response.statusCode;
    } else {
      final statusCode = playerRequestResponse.body['code'];
      playerRequestResponse.status = int.parse(statusCode is int ? statusCode.toString() : statusCode);
      if (playerRequestResponse.status == 0) playerRequestResponse.status = response.statusCode;
    }

    return playerRequestResponse;
  }

  Future<PlayerRequestResponse<dynamic>> request() async {
    try {
      Response response = await _dio.request('');
      return _requestResponse(response);
    } catch (e) {
      PlayerRequestResponse playerRequestResponse = PlayerRequestResponse();
      if (e is DioError) {
        DioError dioError = e;
        playerRequestResponse.status = dioError.response?.statusCode ?? 301;
        playerRequestResponse.body = dioError.response?.data ?? dioError;
      } else {
        playerRequestResponse.body = e;
      }
      return playerRequestResponse;
    }
  }

  Map<String,dynamic>? _setCookie(List cookie) {
    String? storageCookie = _sharedPreferences.getString('cookie');
    Map<String, dynamic>? _cookie = storageCookie == null ? Map<String, dynamic>() : json.decode(storageCookie);
    cookie.forEach((element) {
      String cookieString = element;
      List cookieValue = cookieString.split(';')[0].split('=');
      _cookie![cookieValue[0]] = cookieValue[1];
    });
    return _cookie;
  }

  String? _getCookie({ String? cookie, required SharedPreferences sharedPreferences }) {
    String? _cookie = cookie ?? '';
    String? getCookie = sharedPreferences.getString('cookie');
    Map<String, dynamic> storageCookie = getCookie == null ? Map<String, dynamic>() : json.decode(getCookie);
    if (storageCookie.isNotEmpty) {
      storageCookie.forEach((key, value) {
        if (_cookie != null && _cookie!.isNotEmpty) {
          _cookie = '$_cookie;$key=$value';
        } else {
          _cookie = '$key=$value';
        }
      });
    }
    return _cookie;
  }

}
enum UserAgent { random, pc, mobile }

// RegExp(r'\s*Expires=[^(;|$)]+;*\s*Path=[^(;|$)]+(;|)');
